package com.gitee.qdbp.base.shiro.support;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.WildcardPermission;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;

/**
 * 支持逻辑表达式的权限检查实现类<br>
 * <pre>
 * "resource:create"
 * "root:develop && resource:create"
 * "root:develop and resource:create"
 * "!(root:develop && resource:create)"
 * "not(root:develop and resource:create)"
 * "!root:develop || !resource:create"
 * "!root:develop or !resource:create"
 * "root:develop && (resource:enable || resource:disable)"
 * "root:develop and (resource:enable or resource:disable)"
 * </pre><pre>
 * // grant permission, nonsupport expression
 * ExpressionPermission userPermission = new ExpressionPermission("resource:enable");
 * // check permission, support expression
 * boolean result = userPermission.implies(new ExpressionPermission("resource:enable || resource:disable"));
 * </pre>
 *
 * @author zhaohuihua
 * @version 171105
 */
public class ExpressionPermission implements Permission, Serializable {

    /** 版本序列号 **/
    private static final long serialVersionUID = 1L;

    private static Pattern PERMISSION = Pattern.compile("[\\w\\.\\:\\-,\\*]+");

    private String permission;
    private String thisVarName;
    private WildcardPermission simple;
    private String expression;
    private Map<String, Object> vars;

    public ExpressionPermission(String permission) {
        this.thisVarName = "me";
        this.permission = permission;
        this.parseExpression();
    }

    @Override
    public boolean implies(Permission p) {
        if (this.simple == null) {
            return false; // 表达式仅用于鉴权, 不可用于授权
        }
        if (!(p instanceof ExpressionPermission)) {
            return this.simple.implies(p);
        }

        ExpressionPermission ep = (ExpressionPermission) p;
        if (ep.simple != null) {
            return this.simple.implies(ep.simple);
        }

        OgnlContext context = new OgnlContext();
        Map<String, Object> root = new HashMap<>();
        root.put(thisVarName, this.simple);
        root.putAll(ep.vars);
        try {
            Object result = Ognl.getValue(ep.expression, context, root);
            if (result instanceof Boolean) {
                return (Boolean) result;
            }
            throw new IllegalArgumentException("Ognl expression result error: " + p + " = " + result);
        } catch (OgnlException e) {
            throw new IllegalArgumentException("Ognl expression error: " + p, e);
        }
    }

    // permission = "root:develop && (resource:enable || resource:disable)";
    // --> expression = "me.implies(p0) && (me.implies(p1) || me.implies(p2))"
    // --> p0 = new WildcardPermission("root:develop");
    // --> p1 = new WildcardPermission("resource:enable");
    // --> p2 = new WildcardPermission("resource:disable");
    protected void parseExpression() {
        Map<String, Object> map = new HashMap<>();
        StringBuilder buffer = new StringBuilder();
        int i = 0;
        int index = 0;
        Matcher matcher = PERMISSION.matcher(permission);
        while (matcher.find()) {
            if (matcher.start() == 0 && matcher.end() == permission.length()) {
                this.simple = new WildcardPermission(permission); // 全匹配, 没必要作为表达式处理
                return;
            }

            buffer.append(permission.substring(index, matcher.start()));
            String item = matcher.group(0);
            if (item.equalsIgnoreCase("and") || item.equalsIgnoreCase("or") || item.equalsIgnoreCase("not")) {
                buffer.append(item);
            } else {
                String name = "p" + (i++);
                map.put(name, new WildcardPermission(item));
                buffer.append(thisVarName).append(".implies(").append(name).append(")");
            }
            index = matcher.end();
        }
        if (index < permission.length()) {
            buffer.append(permission.substring(index));
        }

        this.expression = buffer.toString();
        this.vars = map;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
        this.parseExpression();
    }

    @Override
    public String toString() {
        return permission;
    }
}
